﻿///////////////////////////////////////////////////////////////////////////////
//
//  This source is part of the  "Managed Task Framework.NET Runtime"  (MTF.NET Runtime).
//
//
//  Copyright (c) 2011, Multicore Consulting Limited (UK)
//  All rights reserved.
//  http://www.multicoreconsulting.co.uk
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  -  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  -  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in
//     the documentation and/or other materials provided with the
//     distribution.
//  -  Neither the name of Multicore Consulting Limited nor the names of
//     its contributors may be used to endorse or promote products
//     derived from this software without specific prior written
//     permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////
//#define REPLICATOR_TRACE

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MTFOrg.MTF.MTFRuntime;
using MTFOrg.MTF.MTFCore.Messages;
using System.Diagnostics;

namespace MTFOrg.MTF.MTFCore
{
    /// <summary>
    /// Class implementing the replicator object.
    /// </summary>
    partial class Replicator
    {
#if REPLICATOR_TRACE
        const string TRACE_OBJ = "";
#endif
        protected class ReplicatorState
        {
            protected Queue<int> m_QueuedQueries;

            public Queue<int> QueuedQueries
            {
                get { return m_QueuedQueries; }
                set { m_QueuedQueries = value; }
            }

            public ReplicatorState()
            {
                m_QueuedQueries = new Queue<int>();
            }
        }

        protected class ReplicatorOverlayState
        {
            Event m_InputEvent;
            int m_EventCount;

            public Event InputEvent
            {
                get { return m_InputEvent; }
                set { m_InputEvent = value; }
            }

            public int EventCount
            {
                get { return m_EventCount; }
                set { m_EventCount = value; }
            }

            public ReplicatorOverlayState()
            {
                m_InputEvent = null;
                m_EventCount = 0;
            }
        }

        /// <summary>
        /// Number of enabled links per element.
        /// </summary>
        protected int[] m_NumEnabledLinks;

        /// <summary>
        /// Invoked to initialize this task.
        /// </summary>
        public override void OnInitialize()
        {
            base.OnInitialize();

            // Hook up to the producer-consumer event notifications
            m_ProducerConsumerTask.QueryReceived += new ProducerConsumer.OnQuery(OnQueryReceived);
            m_ProducerConsumerTask.ReleaseReceived += new ProducerConsumer.OnRelease(OnReleaseReceived);
            m_ProducerConsumerTask.EventReceived += new ProducerConsumer.OnEvent(OnEventReceived);
            m_ProducerConsumerTask.ReleasedReceived += new ProducerConsumer.OnReleased(OnReleasedReceived);

            // Compute number of enabled links for each element
            m_NumEnabledLinks = new int[m_ProducerConsumerTask.NumElements];
            for (int i = 0; i < m_ProducerConsumerTask.NumElements; i++)
            {
                IConnection firstConnection = m_ProducerConsumerTask.GetProducerConnectionFromLinkNumber(0);
                for (int j = 0; j < m_ProducerConsumerTask.NumProducerLinks; j++)
                {
                    if (!ConnectionHelper.IsProducerLinkDisabled(firstConnection, i, j))
                        m_NumEnabledLinks[i]++;
                }
            }
        }

        /// <summary>
        /// Handles received query.
        /// </summary>
        /// <param name="producerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnQueryReceived(int producerLinkNum, Slots slots, int consumerSlot, int producerSlot, ref object elementState, int elementNumber)
        {
#if REPLICATOR_TRACE
			string statii = string.Empty;
			if (producerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumProducerLinks; _i++)
					statii += " " + (
                        slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{1}]: Query replicator P-link {0}:" + statii, producerLinkNum, elementNumber);
#endif
#if DEBUG
            Debug.Assert(producerSlot != Slots.NO_SLOT, "Producer slots should always be set for a received query");
#endif

            ReplicatorState replicatorState = elementState as ReplicatorState;
            if (replicatorState == null)
            {
                elementState = replicatorState = new ReplicatorState();
            }

            ReplicatorOverlayState overlayState = slots.GetProducerTag(producerSlot) as ReplicatorOverlayState;
            if (overlayState == null)
            {
                overlayState = new ReplicatorOverlayState();
                slots.SetProducerTag(producerSlot, overlayState);

                // Attempt to send a query on the only consumer link
                if (m_ProducerConsumerTask.ConsumerConnectionPoint.Connections.Count > 0)
                {
                    if (!m_ProducerConsumerTask.SendQuery(0, producerSlot, elementNumber))
                    {
                        // If it could not be sent at this time then queue it
                        replicatorState.QueuedQueries.Enqueue(producerSlot);
                    }
#if REPLICATOR_TRACE
                    else
                    {
                        if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query replicator C-link", elementNumber);
                    }
#endif
                }
            }

            // If an input event is ready then send it on
            else if (overlayState.InputEvent != null)
            {
                m_ProducerConsumerTask.SendEvent(producerLinkNum, producerSlot, overlayState.InputEvent, elementNumber);
#if SENDEVENT_TRACE
                Debug.Print("{0}[{1}] link {2}", Name, elementNumber, producerLinkNum);
#endif
                overlayState.EventCount++;

                // If all enabled events are sent then release inactive links as they must be disabled
                if (overlayState.EventCount == m_NumEnabledLinks[elementNumber])
                {
                    for (int i = 0; i < m_ProducerConsumerTask.NumProducerLinks; i++)
                        if (slots.GetProducerLinkStatus(producerSlot, i) == LinkStatus.Inactive)
                            slots.SetProducerLinkStatus(producerSlot, i, LinkStatus.Released);
                }
            }
        }

        /// <summary>
        /// Handles received release.
        /// </summary>
        /// <param name="producerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="eventToRelease">The event to be released</param>
        /// <param name="isRollback">Whether the release was a roll-back</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        /// <returns>Whether to complete the current slot set</returns>
        public bool OnReleaseReceived(int producerLinkNum, Slots slots, int consumerSlot, int producerSlot, Event eventToRelease, bool isRollback, bool deletedSlot, ref object elementState, int elementNumber)
        {
#if REPLICATOR_TRACE
			string statii = string.Empty;
			if (producerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumProducerLinks; _i++)
					statii += " " + (
                        slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{1}]: Release replicator P-link {0}:" + statii, producerLinkNum, elementNumber);
#endif
            // Find out if this was the last release
            ReplicatorOverlayState overlayState = slots.GetProducerTag(producerSlot) as ReplicatorOverlayState;

            if (deletedSlot)
            {
#if REPLICATOR_TRACE
                if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Release replicator C-link", elementNumber);
#endif
                m_ProducerConsumerTask.SendRelease(consumerSlot, overlayState.InputEvent, isRollback, 0, elementNumber, true);
            }

            return true;
        }

        /// <summary>
        /// Called when the producer notifies it has finished with an event.
        /// </summary>
        /// <param name="consumerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnReleasedReceived(int consumerLinkNum, ref object elementState, int elementNumber)
        {
#if REPLICATOR_TRACE
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Released replicator C-link", elementNumber);
#endif
            // Handle any queued queries
            ReplicatorState replicatorState = elementState as ReplicatorState;
            if (replicatorState.QueuedQueries.Count > 0)
            {
                int queuedProducerSlot = replicatorState.QueuedQueries.Peek();

                // Attempt to send a query on the only consumer link
                if (!m_ProducerConsumerTask.SendQuery(0, queuedProducerSlot, elementNumber))
                {
#if DEBUG
                    // If a query could not be sent following a release then an error has occurred
                    //throw new Exception("Unable to send queued query when slot should have been available.");
#endif
                }
                else
                {
                    replicatorState.QueuedQueries.Dequeue();
#if REPLICATOR_TRACE
                    if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query replicator link", elementNumber);
#endif
                }
            }
        }

        /// <summary>
        /// Called when the ProducerConsumer receives an event
        /// </summary>
        /// <param name="consumerLinkNum">Consumer link (absolute) on which the event arrived</param>
        /// <param name="consumerSlots">Consumer slot state information</param>
        /// <param name="producerSlots">Producer slot state information</param>
        /// <param name="eventReceived">Event that was received</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnEventReceived(int consumerLinkNum, Slots slots, int consumerSlot, int producerSlot, Event eventReceived, ref object elementState, int elementNumber)
        {
#if REPLICATOR_TRACE
			string statii = string.Empty;
			if (producerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumProducerLinks; _i++)
					statii += " " + (
                        slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Replicator event" + statii, elementNumber);
#endif
            // Record the received event
            ReplicatorOverlayState overlayState = slots.GetProducerTag(producerSlot) as ReplicatorOverlayState;
            overlayState.InputEvent = eventReceived;

            // Send the received event to all links that are waiting
            for (int i = 0; i < m_ProducerConsumerTask.NumProducerLinks; i++)
            {
                if (slots.GetProducerLinkStatus(producerSlot, i) == LinkStatus.Waiting)
                {
                    m_ProducerConsumerTask.SendEvent(i, producerSlot, eventReceived, elementNumber);
#if SENDEVENT_TRACE
                    Debug.Print("{0}[{1}] link {2}", Name, elementNumber, i);
#endif
                    overlayState.EventCount++;
                }
            }

            // If all enabled events are sent then release inactive links as they must be disabled
            if (overlayState.EventCount == m_NumEnabledLinks[elementNumber])
            {
                for (int i = 0; i < m_ProducerConsumerTask.NumProducerLinks; i++)
                    if (slots.GetProducerLinkStatus(producerSlot, i) == LinkStatus.Inactive)
                        slots.SetProducerLinkStatus(producerSlot, i, LinkStatus.Released);
            }

            // Deal with queued query
            ReplicatorState replicatorState = elementState as ReplicatorState;
            if (replicatorState.QueuedQueries.Count > 0)
            {
                int queuedProducerSlot = replicatorState.QueuedQueries.Peek();
                if (m_ProducerConsumerTask.SendQuery(0, queuedProducerSlot, elementNumber))
                    replicatorState.QueuedQueries.Dequeue();
            }
        }
    }
}
